<html>
<head>
<title>PixelCraft: A Pixel Art Editor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:locale" content="en_US" />
<meta property="og:title" content="PixelCraft: A Pixel Art Editor" />
<meta property="og:description" content="A pixel Art & Animation Creation Tool Built using HTML5 Canvas. It is a Progressive Web App (PWA) with offline compatibility. It is mobile-friendly and is very easy to use." />
<meta name="keywords" content="PixelCraft, pixel-art" />
<meta name="news_keywords" content="PixelCraft, pixel-art" />
<meta name="language" content="English">
<meta property="og:url" content="https://rgab1508.github.io/PixelCraft/" />
<meta property="og:site_name" content="PixelCraft" />
<meta property="article:tag" content="PixelCraft" />
<meta property="article:tag" content="pixel-art" />
<meta property="article:published_time" content="2020-04-22T08:34:48+00:00" />
<meta property="article:modified_time" content="2020-04-22T08:34:51+00:00" />
<meta property="og:updated_time" content="2020-04-22T08:34:51+00:00" />
<meta property="og:image" content="https://rgab1508.github.io/PixelCraft/images/overview.png" />
<meta property="og:image:secure_url" content="https://rgab1508.github.io/PixelCraft/images/overview.png" />
<meta property="og:image:width" content="600" />
<meta property="og:image:height" content="445" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content="A pixel Art & Animation Creation Tool Built using HTML5 Canvas. It is a Progressive Web App (PWA) with offline compatibility. It is mobile-friendly and is very easy to use." />
<meta name="twitter:title" content="PixelCraft: A Pixel Art Editor" />
<meta name="twitter:image" content="https://rgab1508.github.io/PixelCraft/images/overview.png" />
<link rel="shortcut icon" type="image/png" href="icons/logo.png"/>
<link rel="stylesheet" type="text/css" href="global.css" />
<link rel="manifest" href="manifest.json">
<script src="https://kit.fontawesome.com/473e8f3a80.js" crossorigin="anonymous"></script>
<script src="lib/Matrix.js" async></script>
<script src="lib/Shapes.js" async></script>
<script src="lib/Transformation.js" async></script>
<script src="lib/gif.js"></script>
<script src="Canvas.js"></script>
</head>
<body>
    <div class="menubtn">&#9776</div>
    <ul class="menu">
      <li onclick="newProject()">New</li>
      <li onclick="board.save()">Save image</li>
      <li onclick="board.renderGIF()">Save GIF</li>
      <li onclick="showColorSettings()">Color Settings</li>
      <li id="install-pwa-btn" class="display-none" onclick="install()">Install PWA</li>
      <li><a href="https://github.com/rgab1508/PixelCraft" target="_blank"><i class="fab fa-github"></i>Github</a></li>
    </ul>
    <div id="popup">
      <h3>Select the Dimensions Of the grid</h3>
      <input type="text" id="width" value="16">X<input type="text" id="height" value="16">
      <button id="close">OK</button>
    </div>
    <div id="frames" onblur="Frames.close()" tabindex="0">
    <div class="btn" style="left: 10px;" onclick="document.querySelector('#frames #gallery').scrollLeft-=100;"><i class="fa fa-chevron-left"></i></div>
    <div class="btn" style="right: 10px;" onclick="document.querySelector('#frames #gallery').scrollLeft+=100;"><i class="fa fa-chevron-right"></i></div>
    <div id="gallery"></div>
    </div>
    </div>
    <canvas id="canvas"></canvas>
    <div id="toolbar">
      <span class="item" onclick="board.setmode(0)" style="background-color: grey;"><i class="fas fa-pencil-alt"></i></span>
      <span class="item" onclick="board.setmode(1)"><i class="fas fa-eraser"></i></span>
      <span class="item" onclick="board.setmode(2)"><i class="fas fa-fill"></i></span>
      <span class="item" onclick="board.setmode(3)"><i class="fas fa-slash"></i></span>
      <span class="item" onclick="board.setmode(4)"><i class="far fa-circle"></i></span>
      <span class="item" onclick="board.setmode(5)"><i class="far fa-circle" style="transform: rotateX(45deg);"></i></span>
      <span class="item" onclick="board.addFrame()"><i class="fas fa-plus"></i></span>
      <span class="item" onclick="board.undo()"><i class="fas fa-undo"></i></span>
      <span class="item" onclick="board.redo()"><i class="fas fa-redo"></i></span>
      <span class="item" onclick="board.clear()"><i class="fas fa-trash"></i></span>
      <span class="item" onclick="board.addImage()"><i class="fa fa-upload"></i></span>
      <span class="item" onclick="Frames.open()"><i class="fas fa-eye"></i></span>
    </div>
    <div id="palette"></div>
    <div id="color-settings-popup" style="display: none;"> 
      <span onclick="document.getElementById('color-settings-popup').style.display = 'none';" class="close"><i class="fas fa-times" ></i></span>
      <div class="color-palette"></div>
      <div class="button-grp">
        <button>Load Colors</button>
        <button onclick="exportColors()">Export Colors</button>
        <button>Save palette</button>
      </div>
    </div>
</body>
<script type="text/javascript">

// override of finishRendering of gif.js
GIF.prototype.finishRendering = function() {
  var data, frame, i, image, j, k, l, len, len1, len2, len3, offset, page, ref, ref1, ref2;
  len = 0;
  ref = this.imageParts;
  for (j = 0,
  len1 = ref.length; j < len1; j++) {
      frame = ref[j];
      len += (frame.data.length - 1) * frame.pageSize + frame.cursor
  }
  len += frame.pageSize - frame.cursor;
  this.log("rendering finished - filesize " + Math.round(len / 1e3) + "kb");
  data = new Uint8Array(len);
  offset = 0;
  ref1 = this.imageParts;
  for (k = 0,
  len2 = ref1.length; k < len2; k++) {
      frame = ref1[k];
      ref2 = frame.data;
      for (i = l = 0,
      len3 = ref2.length; l < len3; i = ++l) {
          page = ref2[i];
          data.set(page, offset);
          if (i === frame.data.length - 1) {
              offset += frame.cursor
          } else {
              offset += frame.pageSize
          }
      }
  }
  image = new Blob([data],{
      type: "image/gif"
  });
  this.running = false; // *this is the new part*
  return this.emit("finished", image, data)
};

var Tool = {
  "pen": 0,
  "eraser": 1,
  "fillBucket": 2,
  "line": 3,
  "circle": 4,
  "ellipse": 5,
  "addFrame": 6,
  "undo": 7,
  "redo": 8,
  "clearCanvas": 9
};
var tools = [true, false, false, false, false, false];
var lc = [];



class Popup {
  constructor(s) {
    this.s = s;
    document.querySelector(this.s).style.display = "block";
    document.querySelector(this.s).style.transform = "translate(-50%,-50%) scale(1,1)";
  }
  close() {
    document.querySelector(this.s).style.transform = "translate(-50%,-50%) scale(0,0)";
  }
}

class Frames {
  static open() {
  	document.querySelector("#frames").style.display = "block";
    document.querySelector("#frames").style.transform = "translate(-50%,-50%) scale(1,1)";
    document.querySelector("#frames").focus();
    document.querySelector("#frames #gallery").innerHTML="";
    for (var frame of board.frames) document.querySelector("#frames #gallery").appendChild(frame[0]);
    document.querySelectorAll("#frames #gallery img").forEach((x,i) => {
    	x.onclick = (e) => {
    		board.loadFrame(i);
    		Frames.close();
    	};
    	x.oncontextmenu = (e) => {
    		e.preventDefault();
    		var del_confirmation = confirm("Delete?");
    		if (del_confirmation) {
    			board.deleteFrame(i);
    			Frames.open();
    		}
    	};
    });
  }
  static close() {
    document.querySelector("#frames").style.transform = "translate(-50%,-50%) scale(0,0)";
  }
}

function createPalette (ele) {
  ele.innerHTML = window.colors.map(x => (
    `<div class="item" style="background-color: rgb(${x[0]},${x[1]},${x[2]})" ></div>`
  )).join("\n");
}

function showColorSettings (e) {
  document.querySelector("#color-settings-popup").style.display = "block";
  createPalette(document.querySelector('#color-settings-popup > div.color-palette'))
}

function exportColors() {
  let csvContent = "data:text/csv;charset=utf-8," ;
  csvContent += window.colors.join("\n");
  
  console.log(csvContent)
  let encodedUri = encodeURI(csvContent);
  let link = document.createElement("a");
  link.setAttribute("href", encodedUri);
  link.setAttribute("download", "colors.csv");
  document.body.appendChild(link);

  link.click();
}

window.onload = function () {
  let canvasData = localStorage.getItem('pc-canvas-data');
  if(canvasData){
    data = JSON.parse(canvasData);
    // console.log(data);
    window.colors = data.colors;
    if(window.board == undefined){
      window.board = new Canvas(data.width, data.height);
    }
    
    let img = new Image();
    img.setAttribute('src', data.url);
    img.addEventListener("load", function () {
        window.board.ctx.drawImage(img, 0, 0);
    });
    /*
    window.board.frames = JSON.parse(data.frames).map(frame=>{
      let img = new Image();
      img.src = frame[0]
      return [img, frame[1]]
    });
    
    for(let f in data.frames){
      let c = document.createElement('canvas');
      c.width = data.width;
      c.height = data.height;
      let c_ctx = c.getContext('2d');
      c_ctx.drawImage(f[0], 0, 0);
      window.board.addFrame(c.toDataURL());
    }
   */ 
    
    window.board.steps = data.steps;
    window.board.redo_arr = data.redo_arr;
    window.board.setcolor(data.currColor);
    window.gif = new GIF({
      workers: 2,
      quality: 10,
      width: 10 * window.board.width,
      height: 10 * window.board.height
    });
    window.gif.on('finished', function (blob) {
      var url = URL.createObjectURL(blob);
      var link = document.createElement('a');
      link.download = 'canvas.gif';
      link.href = url;
      link.click();
    });
  }
  else {
    newProject();
  }

  let paletteDiv = document.querySelector("#palette");
  paletteDiv.innerHTML = colors.map(x => 
    `<div class="item" style="background-color: rgb(${x[0]},${x[1]},${x[2]})" onclick="board.setcolor([${x}]);act(this);" oncontextmenu="board.setcolor([${x}]);act(this);board.ctx.globalAlpha=+prompt('Transparency(0-1)?')"></div>`
  ).join("\n");

  // paletteDiv.innerHTML += '<div class="btn" ><i class="fa fa-upload"></i></div>';

  document.querySelector("#palette").addEventListener("contextmenu",e=>e.preventDefault());
}

document.querySelector("#close").onclick = function () {
  var width = +document.querySelector("#width").value;
  var height = +document.querySelector("#height").value;
  if(window.board == undefined){
    window.board = new Canvas(width, height);
  }
  window.board.canvas.width = 10 * width;//display each pixel in 10 by 10pxs
  window.board.canvas.height = 10 * height;
  window.board.width = width;//Dimentions of x pixels
  window.board.height = height;//Dimentions of Y pixels
  window.board.canvas.style.display = "block";
  window.board.canvas.style.height = Math.floor((height / width) * window.board.canvas.clientWidth) + "px";
  window.board.w = +window.board.canvas.width;
  window.board.h = +window.board.canvas.height;
  window.board.ctx = window.board.canvas.getContext("2d");
  window.board.ctx.fillStyle = "white";
  window.board.ctx.globalAlpha = 1;
  window.board.ctx.fillRect(0, 0, window.board.w, window.board.h);
  window.board.data = [...Array(window.board.width)].map(e => Array(window.board.height).fill([255, 255, 255, 255]));
  window.board.steps = [];
  window.board.redo_arr = [];
  window.board.frames = [];

  window.board.setcolor([0, 0, 0, 255]);
  window.dim.close();
  window.gif = new GIF({
    workers: 2,
    quality: 10,
    width: 10 * window.board.width,
    height: 10 * window.board.height
  });
  window.gif.on('finished', function (blob) {
    var url = URL.createObjectURL(blob);
    var link = document.createElement('a');
    link.download = 'canvas.gif';
    link.href = url;
    link.click();
  });
}

document.querySelector(".menubtn").onclick = function () {
  document.querySelector(".menu").style.display = document.querySelector(".menu").style.display != "block" ? "block" : "none";
}

function newProject(){
  document.querySelector(".menu").style.display = "none";
  localStorage.removeItem('pc-canvas-data');
  window.dim = new Popup("#popup");
  window.colors = [
    [0, 0, 0, 255],
    [127, 127, 127, 255],
    [136, 0, 21, 255],
    [237, 28, 36, 255],
    [255, 127, 39, 255],
    [255, 242, 0, 255],
    [34, 177, 36, 255],
    [0, 162, 232, 255],
    [63, 72, 204, 255],
    [163, 73, 164, 255],
    [255, 255, 255, 255],
    [195, 195, 195, 255],
    [185, 122, 87, 255],
    [255, 174, 201, 255],
    [255, 201, 14, 255],
    [239, 228, 176, 255],
    [181, 230, 29, 255],
    [153, 217, 234, 255],
    [112, 146, 190, 255],
    [200, 191, 231, 255]
  ];
}
function filler(x, y, cc) {
  if (x >= 0 && x < board.width && y >= 0 && y < board.height) {
    if (JSON.stringify(board.data[x][y]) == JSON.stringify(cc) && JSON.stringify(board.data[x][y]) != JSON.stringify(board.color)) {
      board.draw(x, y);
      filler(x + 1, y, cc);
      filler(x, y + 1, cc);
      filler(x - 1, y, cc);
      filler(x, y - 1, cc);
    }
  }
}

function act(clr) {
  document.querySelectorAll("#palette .item").forEach(x => {x.style.boxShadow = ""; x.style.border = "";});
  clr.style.boxShadow = "10px 10px 10px 10px rgba(0,0,0,0.5)";
  clr.style.border = `1px white solid`;
}

// window.onbeforeunload = function () {
//   board.saveInLocal();
//   return "Data will be lost if you leave the page, are you sure?";
// };	

var scope = {
  scope: './'
};
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register(
    'sw.js',
    scope
  ).then(function(serviceWorker) {
    console.log('successful');
  }).catch(function(error) {
    alert("error");
  });
} else {
  console.log('unavailable');
}

var msg;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  msg = e;
  // Shows the install button only when the app is installable
  document.querySelector("#install-pwa-btn").classList.remove("display-none");
});

function install() {
  msg.prompt();
  msg.userChoice.then((choiceResult) => {
    if (choiceResult.outcome === 'accepted') {
      // Hides the install button
      document.querySelector("#install-pwa-btn").classList.add("display-none");
    }
  });
}

window.onerror = function (errorMsg, url, lineNumber) {
  alert('Error: ' + errorMsg + ' Script: ' + url + ' Line: ' + lineNumber);
}
</script>
</html>
